package org.aceor.mddal.common.sqlparser.result;

import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.Sets;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;

/**
 * Created by lxue on 16/3/23.
 */
public class SQLBuilder implements Appendable {
    private static final Logger logger = LoggerFactory.getLogger(SQLBuilder.class);

    private static final String AND = "AND";
    private static final String OR = "OR";
    private static final String WHERE = "WHERE";

    private final Collection<Object> segments = new LinkedList<>();

    private final Map<String, ReplaceToken> replaceTokenMap = new HashMap<>();

    private ColumnAppendToken columnAppendToken;

    private StringBuilder currentSegment;

    /**
     * 遇到分库分表字段Condition时,这个字段被置为true
     */
    private boolean duringExprSkip = false;

    private ReplaceToken whereAppendToken;

    private WhereConditionEndToken whereConditionEndToken;

    public SQLBuilder() {
        currentSegment = new StringBuilder();
        segments.add(currentSegment);
    }

    /**
     * 生成SQL语句.
     * 这里的实现实在看不下去了.
     * 处理分库分表字段Condition的策略:
     * 1. 将分库分表字段ComparableUnit从sql打印中去掉.设定duringExprSkip=true
     * 2. 扫描到AND输出,会判断duringExprSkip的值是否为true,为true则跳过AND打印,并置其为false
     * [这里是为了解决形如 shardingCondition1 and shardingCondition2 and xxx] 如果不一并跳过and打印,则会导致打印N个AND而产生语法错误
     * 3. 每次分库分表字段ComparableUnit去掉都会置true,如果之后没有遇到AND跳过,那么duringExprSkip就保持true
     * 4. Condition去掉后,经过可取值罗列,以及路由,会得到新的IN表达式的Condition,形如 id IN (1,3,5).
     * 这个字符串会通过replaceWhereAppendToken()添加到Where语句后面
     * 此时,如果duringExprSkip = false,则会多打印一个AND.
     * 形如 id IN(1,2,3,4,5) and age >12
     *      ==> 中间状态age>12(id Condition和and同时跳过)
     *      ==> 例如奇偶路由得到'WHERE' + ' id IN(1,3,5)' + ' and ' + 'age >12'
     * <p>
     * 上述是算法描述.不过会有两个问题.
     * 1. 会出现无用的括号对.比如 (id IN(1,2,3,4,5) and id >2),通过去除后,就只剩一个().这个需要后期删除,即filterUnusedBracketPairs
     * 2. 如果Where后面紧跟的不是分库分表字段.就会出现前面少一个AND,后面多一个AND,导致语法错误
     * 形如 where age > 12 and id IN(1,2,3,4,5) ==>会变成 where id IN(1,3,5) age>12 and
     * 这种情况通过分析尾部是否是AND来进行甄别,如果是则去掉尾部AND,并在中间添加AND.所以toSQL方法,分成了left,right两部分处理,最后合并
     *
     * @return SQL语句
     */
    public String toSQL() {
        StringBuilder left = new StringBuilder();
        StringBuilder middle = new StringBuilder();
        StringBuilder right = new StringBuilder();
        boolean scanLeft = true;
        boolean scanMiddle = true;
        for (Object each : segments) {
            if (scanLeft) {
                left.append(each.toString());
            } else if (scanMiddle) {
                middle.append(each.toString());
            } else {
                right.append(each.toString());
            }
            if (each instanceof WhereConditionReplaceToken) {
                scanLeft = false;
            }
            if (each instanceof WhereConditionEndToken) {
                scanMiddle = false;
            }
        }
        String middle0 = filterUnusedBracketPairs(middle.toString()).trim();
        //AND结尾判断必须先做去括号对处理.这里得想办法调整顺序
        String sql;
        if (middle0.endsWith(AND)) {
            String middle1 = middle0.substring(0, middle0.length() - 3);
            sql = Joiner.on(" ").join(left, AND, middle1, right);
        }else {
            sql = Joiner.on(" ").join(left, middle0, right);
        }
        return sql;
    }

    /**
     * 去除掉无用的括号对,即正则: ( *) 括号里面包含零个或多个空格
     * 如 "select * from a where b = 2 () and (  ) c = 3 "
     * 清理过后 "select * from a where b = 2  and  c = 3 "
     *
     * @param s
     * @return
     */
    private String filterUnusedBracketPairs(String s) {
        return s.replaceAll("\\( *\\)", "");
    }

    /**
     * 进行跳过处理,添加跳过标志
     * 1. or
     * @return
     */
    public SQLBuilder appendExprSkipToken() {
        duringExprSkip = true;
        return this;
    }

    /**
     * 添加替换标记
     *
     * @param replaceTokenKey
     * @return
     */
    public SQLBuilder appendReplaceToken(final String replaceTokenKey) {
        ReplaceToken replaceToken;
        if (replaceTokenMap.containsKey(replaceTokenKey)) {
            replaceToken = replaceTokenMap.get(replaceTokenKey);
        } else {
            replaceToken = new ReplaceToken();
            replaceToken.value = replaceTokenKey;
            replaceTokenMap.put(replaceTokenKey, replaceToken);
        }
        segments.add(replaceToken);
        currentSegment = new StringBuilder();
        segments.add(currentSegment);
        return this;
    }

    /**
     * 一般用于表名替换(逻辑表名和实际表名)
     *
     * @param replaceTokenKey   待替换的标记位
     * @param replaceTokenValue 替换成的新值
     * @return
     */
    public SQLBuilder replaceToken(final String replaceTokenKey, final String replaceTokenValue) {
        if (replaceTokenMap.containsKey(replaceTokenKey)) {
            replaceTokenMap.get(replaceTokenKey).value = replaceTokenValue;
        }
        return this;
    }

    /**
     * 注册列增补对象
     */
    public void registerColumnAppendToken() {
        if (null == columnAppendToken) {
            //1. currentSegment ==> print 'select *'

            //2. columnAppendToken ==> print 'appendToken'
            columnAppendToken = new ColumnAppendToken();
            segments.add(columnAppendToken);

            //3. new currentSegment ==> print 'from'
            //用于打断当前的currentSegment
            currentSegment = new StringBuilder();
            segments.add(currentSegment);

            //4. result ==> print 'select *,appendToken from'
        } else {
            logger.warn("SQLBuilder$registerColumnAppendToken==>重复添加'列新增'事件");
        }
    }

    /**
     * 添加增补的列
     *
     * @param columnName
     * @return
     */
    public SQLBuilder appendColumn(final String columnName) {
        if (null == columnAppendToken) {
            throw new NullPointerException("未注册registerColumnAppendToken");
        }
        columnAppendToken.addColumn(columnName);
        return this;
    }

    public SQLBuilder replaceWhereAppendToken(String newCondition) {
        if (null == whereAppendToken) {
            return this;
//            throw new IllegalArgumentException("没有解析到WHERE关键字 ==> 无法使用了whereAppendToken替换, newCondition :" + newCondition);
        }
        if (duringExprSkip) {
            whereAppendToken.value = newCondition + " ";
        } else {
            whereAppendToken.value = newCondition + " AND ";
        }
        return this;
    }

    public void printWhereConditionEndToken() {
        if (null == whereConditionEndToken) {
            whereConditionEndToken = new WhereConditionEndToken();
            segments.add(whereConditionEndToken);
            currentSegment = new StringBuilder();
            segments.add(currentSegment);
        }
    }
    @Override
    public Appendable append(final CharSequence sql) throws IOException {
        //在Or出现的两重限定之下,这里不会出现OR,因此只需要跳过append and就行
        if (duringExprSkip
                && Objects.equal(sql.toString().trim(), AND)) {
            //skip append
            duringExprSkip = false;
        } else {
            currentSegment.append(sql);
        }
        //由于已经把分库分表字段的Condition语句全部跳过了,这里在where后面留出一个空地,以便后面补上新造的Condition
        if (Objects.equal(sql.toString().trim(), WHERE)) {
            whereAppendToken = new WhereConditionReplaceToken();
            currentSegment.append(" ");
            segments.add(whereAppendToken);
            currentSegment = new StringBuilder();
            segments.add(currentSegment);
        }
        return this;
    }

    @Override
    public Appendable append(final CharSequence sql, final int start, final int end) throws IOException {
        throw new UnsupportedOperationException();
    }

    @Override
    public Appendable append(final char c) throws IOException {
        currentSegment.append(c);
        return this;
    }

    @Override
    public String toString() {
        StringBuilder result = new StringBuilder();
        for (Object each : segments) {
            result.append(each.toString());
        }
        return result.toString();
    }

    private static class ReplaceToken {
        protected String value;

        @Override
        public String toString() {
            return null == value ? "" : value;
        }
    }

    private static class WhereConditionReplaceToken extends ReplaceToken {

    }

    /**
     * where语句结束标志
     */
    private static class WhereConditionEndToken extends ReplaceToken{

    }

    private static class ColumnAppendToken {
        private Set<String> columns = Sets.newHashSet();

        public void addColumn(String columnName) {
            columns.add(columnName);
        }

        @Override
        public String toString() {
            if (CollectionUtils.isNotEmpty(columns)) {
                StringBuilder sb = new StringBuilder();
                for (String value : columns) {
                    sb.append(",").append(value);
                }
                return sb.toString();
            } else {
                return "";
            }
        }
    }
}
